home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Cream of the Crop 1
/
Cream of the Crop 1.iso
/
PROGRAM
/
BCCAPP.ARJ
/
ACCESS.C
next >
Wrap
C/C++ Source or Header
|
1991-09-15
|
9KB
|
416 lines
/*
*
* Structured file I/O. Provides structure I/O for both DATABASE and
* INDEX classes. In addition, we will provide the means to add/delete
* records as necessary
*
* (C) 1990 Vision Software
*
* $Id: access.c 1.2003 91/05/08 14:02:17 pcalvin beta $
*
* Comments:
*
* This class provides lowlevel (ANSI-C) file I/O for the DATABASE and
* index classes. By centralizing this class, we may provide extended
* service (such as caching) without breaking the other code. In addition
* We provide services to maintain/add/delete records..
*
* Bugs:
*
* None documented
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <io.h>
#include <stdhdr.h>
#include <access.h>
#include <adl.h>
/*
* Opens stream based upon szFileName
*/
ACCESS::ACCESS(PINF pinf,CCH cchRecord,SZ szFileName,SZ szExtension,BOOL fCreateIfNeeded)
{
Assert(pinf != pinfNil);
Assert(szFileName != szNil);
Assert(cchRecord != cchNil);
SZ sz = SzFullPathFromSzSz(szFileName,szExtension);
pinfBase = pinf;
cch = cchRecord;
rgchStorage = new char[cch];
stmFile = fopen(sz,"rb+");
/*
* Will only create the file if the user wants it..
*/
if (stmFile == stmNil)
{
if (fCreateIfNeeded)
{
Verify(FCreateFile(sz));
}
else
{
IOError("Unable to Open %s",sz);
}
}
else
{
/*
* Determine the number of records already in the file.
*/
recMax = RecFromStmCch(stmFile,cch);
recCurrent = recError;
/*
* Read in the header. To avoid disk-seeks, we store the header
* in a variable.
*/
rewind(stmFile);
Verify(fread(&fhd,sizeof(fhd),1,stmFile) == 1);
}
/*
* Check the file header. If sizes are different, ask about
* an update procedure.
*/
if (fhd.cchSizeofRecord != cch)
{
if (FAskSz("Record size has changed, update file?","Press \"Y\" to fixup datafile, \"N\" to ignore"))
{
fclose(stmFile);
Verify(FUpdateDatabase(pinfBase,sz,cch,fhd.cchSizeofRecord));
}
}
}
/*
* Destruction is simple, standard fclose()
*/
ACCESS::~ACCESS()
{
delete rgchStorage;
fclose(stmFile);
}
/*
* Marks the current record
*/
BOOL ACCESS::FMark()
{
recMarked = RecQuery();
return (fTrue);
}
/*
* Goes to the previously marked record
*/
BOOL ACCESS::FGotoMark()
{
return (FGotoRec(recMarked));
}
/*
* Answers with the base of the record
*/
PINF ACCESS::PinfQuery(VOID)
{
return (pinfBase);
}
/*
* Saves the current record. Does not adjust the record pointer
*/
BOOL ACCESS::FSave(VOID)
{
if (fseek(stmFile,sizeof(FHEADER)+recCurrent*cch,SEEK_SET) == 0)
{
Verify(fwrite(pinfBase,cch,1,stmFile) == 1);
return (fTrue);
}
else
{
return (fFalse);
}
}
/*
* Answers if the current record has been deleted..
*/
BOOL ACCESS::FDelete(VOID)
{
REC recDelete = RecQuery();
/*
* If none have been deleted, make this the first..
*/
if (fhd.recFirstDeleted == recError)
fhd.recFirstDeleted = recDelete;
/*
* Clear all info from the record..
*/
memset(pinfBase,0,cch);
pinfBase->recNextDeleted = recError;
Verify(FSave());
/*
* Keep link to last in deleted chain..
*/
if (FGotoRec(fhd.recLastDeleted))
{
pinfBase->recNextDeleted = recDelete;
Verify(FSave());
}
/*
* Update pointer to new end and save.
*/
fhd.recLastDeleted = recDelete;
rewind(stmFile);
Verify(fwrite(&fhd,sizeof(fhd),1,stmFile) == 1);
return (fTrue);
}
/*
* Goes to the first record in the list. Due to the ability to delete
* records, this need not be the first physical record..
*
* NOTE: Does not MODIFY to record in pinfBase if the record is not found
*/
BOOL ACCESS::FFirst(VOID)
{
return (FGotoRec(fhd.recFirstActive));
}
/*
* Makes the desired record the first logical record. This happens (internally)
* if the first record is deleted. It could also be called (externally) if
* an INDEX root is deleted/created
*/
BOOL ACCESS::FMakeFirstRec(REC rec)
{
fhd.recFirstActive = rec;
rewind(stmFile);
Verify(fwrite(&fhd,sizeof(fhd),1,stmFile) == 1);
return (fTrue);
}
/*
* Answer if the specific physical record can be found
*/
BOOL ACCESS::FGotoRec(REC recDest)
{
if (recDest >= recMax || recDest == recError)
{
return (fFalse);
}
else if ((fseek(stmFile,sizeof(FHEADER)+recDest*cch,SEEK_SET) == 0) && (fread(pinfBase,cch,1,stmFile) == 1))
{
recCurrent = recDest;
return (fTrue);
}
else
{
return (fFalse);
}
}
/*
* Answers if a new record has been created..
*/
REC ACCESS::RecCreate()
{
/*
* Try to create a new record from the scraps left over
* by previous deletes.
* Before search, save contents of created record.
*/
if (fhd.recFirstDeleted != recError)
{
memcpy(rgchStorage,pinfBase,cch);
Verify(FGotoRec(fhd.recFirstDeleted));
fhd.recFirstDeleted = pinfBase->recNextDeleted;
rewind(stmFile);
Verify(fwrite(&fhd,sizeof(fhd),1,stmFile) == 1);
memcpy(pinfBase,rgchStorage,cch);
}
else
{
recCurrent = recMax;
recMax += 1;
Verify(FSave());
}
/*
* Assert that garbage does not get left here..
*/
pinfBase->recNextDeleted = recError;
/*
* If there are no other active records, I guess this is the first..
*/
if (fhd.recFirstActive == recError)
{
Verify(FMakeFirstRec(recCurrent));
}
return (recCurrent);
}
/*
* Answers with the current physical record number
*/
REC ACCESS::RecQuery(VOID)
{
return (recCurrent);
}
/*
* Answers with the absolute number of records in the file.
* NOTE: Includes deleted records
*/
REC ACCESS::RecMaxQuery(VOID)
{
return (recMax);
}
/*
* Answers with the number of records in a stream.
*
* NOTE: This is a DOS KLUDGE. Porting to UN*X or other civilized??
* operating system will require modification here
*/
REC ACCESS::RecFromStmCch(FILE *stmInput,CCH cchInput)
{
LONG cbytes = filelength(fileno(stmInput));
return ((INT)(cbytes / cchInput));
}
/*
* Using the static member szDataPath, we answer with a string that
* contains the entire pathname (including extension) for the
* desired file.
*
* NOTE: This is a TEMPORARY string. Successive calls to this function
* provide the same address, with a different string.
*/
SZ ACCESS::SzFullPathFromSzSz(SZ sz,SZ szExtension)
{
PointerAssert(sz);
PointerAssert(szExtension);
STATIC SZTEMP szAnswer;
if (szDataPath == szNil)
{
strcpy(szAnswer,sz);
strcat(szAnswer,szExtension);
}
else
{
strcpy(szAnswer,szDataPath);
strcat(szAnswer,sz);
strcat(szAnswer,szExtension);
}
return (szAnswer);
}
/*
* Creates the file and initializes the header
*/
BOOL ACCESS::FCreateFile(SZ sz)
{
stmFile = fopen(sz,"wb+");
if (stmFile == stmNil)
{
IOError("Unable to Create %s",sz);
NotReached();
}
else
{
fhd.cchSizeofRecord = cch;
fhd.recFirstDeleted = recError;
fhd.recLastDeleted = recError;
fhd.recFirstActive = recError;
Verify(fwrite(&fhd,sizeof(fhd),1,stmFile) == 1);
/*
* Assert that no records are available..
*/
recMax = recNil;
recCurrent = recError;
}
return (fTrue);
}
/*
* Updates the database because the number of bytes within
* each record has been perverted. If the value
* has gone up, the user may place a "default" value. If
* the database has gone down, the record is truncated.
*/
BOOL ACCESS::FUpdateDatabase(PINF pinf,SZ sz,CCH cchNew,CCH cchOld)
{
SZ szWork = tmpnam(NULL);
SZTEMP szTmp;
SZTEMP szOriginal;
/*
* Be sure the TMP file gets placed in the correct directory
*/
Verify(strcpy(szOriginal,sz) != szNil);
Verify(strcpy(szTmp,SzFullPathFromSzSz(szWork,"")) != szNil);
/*
* Assert the file may be renamed..
*/
if (rename(szOriginal,szTmp) == 0)
{
ACCESS acc(pinf,cchOld,szWork,"",fFalse);
REC rec = recNil;
/*
* Creates the NEW database file that is going to be fixedup
*/
Verify(FCreateFile(szOriginal));
/*
* Now, traverse the old database and save the contents
* in the new one..
*/
while (acc.FGotoRec(rec))
{
Verify(RecCreate() == rec);
Verify(FSave());
rec++;
}
}
/*
* And finally, delete the old one..
*/
return (!remove(szTmp));
}